During a dialog, the dialog lines are scanned from top to bottom.
If the dialog-line is spoken by the player, all the matching lines are displayed for the player to pick from.
If the dialog-line is spoken by another, the first (top-most) matching line is selected.
Each dialog line contains the following fields:
1) Dialogue partner: This should match the person player is talking to.
Usually this is a troop-id.
You can also use a party-template-id by appending '|party_tpl' to this field.
Use the constant 'anyone' if you'd like the line to match anybody.
Appending '|plyr' to this field means that the actual line is spoken by the player
Appending '|other(troop_id)' means that this line is spoken by a third person on the scene.
(You must make sure that this third person is present on the scene)
2) Starting dialog-state:
During a dialog there's always an active Dialog-state.
A dialog-line's starting dialog state must be the same as the active dialog state, for the line to be a possible candidate.
If the dialog is started by meeting a party on the map, initially, the active dialog state is "start"
If the dialog is started by speaking to an NPC in a town, initially, the active dialog state is "start"
If the dialog is started by helping a party defeat another party, initially, the active dialog state is "party_relieved"
If the dialog is started by liberating a prisoner, initially, the active dialog state is "prisoner_liberated"
If the dialog is started by defeating a party led by a hero, initially, the active dialog state is "enemy_defeated"
If the dialog is started by a trigger, initially, the active dialog state is "event_triggered"
3) Conditions block (list): This must be a valid operation block. See header_operations.py for reference.
4) Dialog Text (string):
5) Ending dialog-state:
If a dialog line is picked, the active dialog-state will become the picked line's ending dialog-state.
6) Consequences block (list): This must be a valid operation block. See header_operations.py for reference.
7) Voice-over (string): sound filename for the voice over. Leave here empty for no voice over
In this section we will be examining module_dialogs.py, by far the largest file in the module system and one of the most important, as it contains all dialogue in Mount&Blade. Any new dialogue that you wish to create will go into this file. Understand that this file is read from top to bottom each time a dialog is started. When the criteria for a tuple is met, then it will be executed.
The module_dialog.py file is a bit more complex than it used to be. Looking at the first few tuples, you will see that the target "anyone" is used with the various start-dialog states (explained later). There conditions are getting checked and registries, string registries and global variables set. What may seem odd is that each one's dialog text states that the dialog is not displayed. These dialogs set up these pieces of information to be used further down in the file. This way, these code blocks only need to be typed in once, not in every conversation.
If the dialog partner does not fit the criteria for that tuple, then it is skipped. This makes it easier for you to add new generic NPCs, village elders, etc. without having to worry about setting up their conversations. Take a quick look at one of them near the top:
dialogs = [
[anyone ,"start", [(store_conversation_troop, "$g_talk_troop"),
(store_conversation_agent, "$g_talk_agent"),
(store_troop_faction, "$g_talk_troop_faction", "$g_talk_troop"),
# (troop_get_slot, "$g_talk_troop_relation", "$g_talk_troop", slot_troop_player_relation),
(call_script, "script_troop_get_player_relation", "$g_talk_troop"),
(assign, "$g_talk_troop_relation", reg0),
...
(try_begin),
(this_or_next|is_between, "$g_talk_troop", village_elders_begin, village_elders_end),
(is_between, "$g_talk_troop", mayors_begin, mayors_end),
(party_get_slot, "$g_talk_troop_relation", "$current_town", slot_center_player_relation),
(try_end),
There is more, but this will give you the idea. This will be checked when you start a conversation with anyone. Looking at the try block, we see that it checks if "$g_talk_troop" is listed between the constants "village_elders_begin" and "village_enders_end". Since the operation this_or_next is used, this condition and the next condition are read as an OR condition. This would mean that if the first condition is not met but the second condition (is_between, "$g_talk_troop", mayors_begin, mayors_end) is true, the rest of the code block is still executed.
The used constants are defined in module_constants.py. This is why, if you want to add a village elder, it should be placed between "trp_village_1_elder" and "trp_merchants_end" as defined in that file to take advantage of the settings pre-defined for village elders.
Let's scroll down a bit, and find a more specific dialog, namely the start conversation with Ramun the slave trader:
[trp_ramun_the_slave_trader, "start", [
(troop_slot_eq, "$g_talk_troop", slot_troop_met_previously, 0),
], "Good day to you, {young man/lassie}.", "ramun_introduce_1",[]],
Breaking down the tuple field of Ramun's opening statement:
1)Dialogue partner : trp_ramun_the_slave_trader
2)Starting Dialog-State : "start"
3)Conditions block : (troop_slot_eq, "$g_talk_troop", slot_troop_met_previously, 0),
4) Dialog text string : "Good day to you, {young man/lassie}."
5) Ending dialog-state : "ramun_introduce_1"
6) Consequences : []
The most important things to note in this tuple are the dialog-states, both the starting and ending dialog-states. We will delve into these more deeply now.
The ending dialog-state "ramun_introduce_1" is what leads a conversation from one line to the next. The ending dialog-state can be anything you like but there must be another tuple with a matching starting dialog-state. For example, if we were to make a tuple with the ending state "blue_oyster", this would lead into any tuple with the starting state "blue_oyster". There must be an exact match; if no match exists, build_module.bat will throw an error upon trying to build.
If there are multiple tuples with the starting state "blue_oyster", something special happens. If the tuples are spoken by the player, they result in a menu where the player can choose between the provided tuples. In Vanilla M&B the game only has room for five dialogue options at a time while the limit is 1024 at the Warband engine. The lines can't be too long, either, or they will either shrink or spill out of their box. If it is the player's answer, it will shrink as much as it can before getting out. If it is the other partner's dialog line, it will spill out right away.[1] If the tuples are spoken by an NPC, the Module System will use the first tuple in module_dialogs.py for which all conditions are met, even if there are multiple lines that qualify.
To end a conversation, you must use the ending dialog-state "close_window". To start a conversation, there are several special starting dialog-states from which you can choose. We will refer to these as initial dialog-states. Here is the full list of initial dialog-states:
As you can see, each initial dialog-state is designed for a specific situation. It will not be considered anywhere outside its situation.
The dialogue interface is very flexible, and can be used in a great number of ways. It handles both events on the overland map and events in scenes. It allows dialogues to be triggered whenever you want them. In the following segments, we examine how to use the dialogue interface to its fullest.
As we have outlined in the last segment, a dialogue line will only be considered if all its requirements are met. First of all, the player must be speaking to the correct troop. A line with trp_ramun_the_slave_trader will not be considered if the player is addressing trp_constable_hareck. The constant anyone may be used if the line is to be spoken by anyone the player is addressing at the time.
Second, the starting dialog-state must be in accordance with the situation - either the dialogue line is initiated by an initial dialog-state, or the line follows from a matching ending dialog-state in another line. If the starting dialog-state does not meet specifications, it won't be considered for use.
Thirdly, the same logic of the previous two points applies to the conditions block. Unless all of a line's conditions are met, the line will not be considered. If either the conditions or the starting dialog-state are erroneous, you will experience problems; either build_module.bat will throw an error, or the erroneous dialogue lines will simply freeze in-game, since their ending dialog-states will not be able to find another tuple to activate. This is the reason why you must be careful with conditions blocks. Make sure you don't break your own dialogue by planting conditions which are not properly set.
However, when everything is working in harmony, conditions blocks can be very powerful since they can contain try blocks. You can call slots from inside a conditions block, and then use the result in a condition operation inside the same block. You can set registers and string registers in order to use them in the actual dialogue.
You can observe the use of a conditions block in the tuple of module_dialogs.py which is used to talk with Ramun the slave trader:
[trp_ramun_the_slave_trader, "start", [(troop_slot_eq, "$g_talk_troop", slot_troop_met_previously, 0),],
This block contains only one condition, which requires the variable slot_troop_met_previously of "$g_talk_troop" to be equal to 0. This is because all registers and variables are equal to 0 at the beginning of a new game. And if you look at the next tuple, you will notice the consequences block:
[trp_ramun_the_slave_trader|plyr, "ramun_introduce_1", [], "Forgive me, you look like a trader, but I see none of your merchandise.", "ramun_introduce_2",[
(troop_set_slot, "$g_talk_troop", slot_troop_met_previously, 1),
]],
The troop_set_slot operation in this block sets the variable slot_troop_met_previously of "$g_talk_troop" (to whom you are talking) to 1 after the line has been displayed. In other words, after this line has been displayed once, it will never be displayed again - because afterwards, the variable slot_troop_met_previously will no longer be equal to 0. The dialogue system will then ignore this line and instead go to the next tuple in the file which meets requirements:
[trp_ramun_the_slave_trader,"start", [], "Hello, {playername}.", "ramun_talk",[]],
The only requirements on this line are that the player must be speaking to Ramun in a scene. It will always be selected when speaking to Ramun after he has delivered his speech.
It is important to know that various variables are set in the first three tuples of this file. They are set when you talk with anyone. Each has its specific dialog start state, but only covers conversations between you and someone you are specifically targeting for a conversation. "$g_talk_troop" is just one of the variables cataloged and set in this process. In the first few lines of the first tuple, they also store your relation with "$g_talk_troop" from reg0, which is calculated in the script troop_get_player_relation (check it out in module_scripts.py) and stored in "$g_talk_troop_relation":
[anyone ,"start", [(store_conversation_troop, "$g_talk_troop"),
(store_conversation_agent, "$g_talk_agent"),
(store_troop_faction, "$g_talk_troop_faction", "$g_talk_troop"),
# (troop_get_slot, "$g_talk_troop_relation", "$g_talk_troop", slot_troop_player_relation),
(call_script, "script_troop_get_player_relation", "$g_talk_troop"),
(assign, "$g_talk_troop_relation", reg0),
Things are done this way so you won't have to do this kind of thing every time you create a new dialog block. If you will be having special dialogs that will take into consideration things like your relation with the target, then it would be good to scan through these (or the specific start-dialog state) and see if what you will be checking is already set.
Pages 27-30
The following declarations for dialogs can be set:
anyone | dialog will start with anyone if you send the game to its dialog state. I.e. it won't check for the specific troop id. |
auto_proceed | dialog answer won't require player to press left mouse button to continue further as dialogs usually do. It is instant, so the player can't even see the text. It is assumed that this flag is for assigning some important variables that will be used in the dialog it jumps to. |
multi_line |
is for plyr answers only. It makes them behave like your partner's answers i.e. lets the text jump to the next line instead of shrinking it as it would be otherwise as can be seen in the picture below. |
party_tpl | the dialog will be issued only when you meet a party of specified template on global map. See Native examples for the syntax. |
plyr | dialog answer will be played from the player agent. |
repeat_for_factions | creates a list of factions as player answers, the amount of options will depend on how many factions module_factions.py file has. |
repeat_for_parties | creates a list of parties as player answers, the amount of options will depend on how many parties module_parties.py file has. |
repeat_for_troops | creates a list of troops as player answers, the amount of options will depend on how many troops module_troops.py file has. |
repeat_for_100 | restricts the list of answer options to 100. |
repeat_for_1000 | restricts the list of answer options to 1000. |
All the repeat_for_x declarations create a list of player options (so plyr is required), where each option is a separate object. The amount of options depends on the last number in the respective ID_xxx.py file (max 1024). The declarations should be used on pair with the operation store_repeat_object (in both brackets of the dialog answer, so in the condition and consequences ones). An example can be looked up here (need to integrate that in some way).
The first section
"[ ...some code here... ]"
is a block that exists to test a condition or series of conditions. It's only purpose is to return TRUE or FALSE. And every single dialog gets checked until a TRUE result occurs.
Secondly, the parts after "start" can only be accessed IF "start" happened, their branch is active AND their own condition block is TRUE. So, if you want something that always happens when Generalissimo EvilGuy meets your player on a Tuesday, but you want him to do something else on Wednesday, his "start" might look like this:
#Start of Dialog block
[
#See header_dialogs for what can be here besides "anyone". All Dialogs must have a "start".
anyone, "start",
#Start condition block here
[
(eq, "$g_talk_troop", "trp_generalissimo_evilguy"),#ONLY RUN ME IF EVILGUY
(try_begin),
(eq, "$g_day_of_week", 2),#DO THIS TUESDAYS
(assign, "$g_conversation_temp",1),
str_store_string, s17, "@This is my Tuesday conversation start"),
(else_try),
(eq, "$g_day_of_week", 3),#WEDNESDAYS
(assign, "$g_conversation_temp",2),
(str_store_string, s17, "@This is my Wednesday conversation start"),
(else_try),
(assign, "$g_conversation_temp",3),#IF ALL ELSE FAILS...
(str_store_string, s17, "@This is what I say any other day of the week"),
(try_end),
],
#Condition is TRUE; we're talking to EvilGuy, now we need our custom text:
"s17",
#Now we the Player to be able to say something, even if it's something prosaic like, "leave", to exit this Dialog.
"player_response_or_responses_here",
#Consequences, if any:
[],
#End of dialog block
],
Some people don't like doing Dialogues using a Troop in that first block; it makes it easier to accidentally create logic errors. They prefer to do it in the Conditions block, like this:
[anyone,"start", [(eq, "$g_talk_troop", "trp_player_castellan"),], "What can I do for you {playername}", "castellan_talk",[]],
That means that I can have multiple starts for that Troop, depending on the conditions block returning TRUE, like this:
[anyone,"start",
[
(eq, "$g_talk_troop", "trp_player_castellan"),
(eq, "$g_some_game_global", 1),
], "What can I do for you {playername}", "castellan_talk",[]],
[anyone,"start",
[
(eq, "$g_talk_troop", "trp_player_castellan"),
(eq, "$g_some_game_global", 2),
], "Oh, it's {playername}, that scummy devil, come to bother me again!", "castellan_talk",[]],
[/code]
The only major exception to this is dealing with Party encounters where you may be talking to any random guy in the Party, for example, the Looter's start:
[code][party_tpl|pt_looters|auto_proceed,"start", [(eq,"$talk_context",tc_party_encounter),(encountered_party_is_attacker),], "{!}Warning: This line should never be displayed.", "looters_1",[
(str_store_string, s11, "@It's your money or your life, {mate/girlie}. No sudden moves or we'll run you through."),
(str_store_string, s12, "@Lucky for you, you caught me in a good mood. Give us all your coin and I might just let you live."),
(str_store_string, s13, "@This a robbery, eh? I givin' you one chance to hand over everythin' you got, or me and my mates'll kill you. Understand?"),
(store_random_in_range, ":random", 11, 14),
(str_store_string_reg, s4, ":random"),
(play_sound, "snd_encounter_looters"),
]],
Note that there are not two but four things that must be TRUE:
extra chapter for this? Yoshiboy, Modding Q&A
Dialog states, cwr (credit), Modding Q&A
Rare dialog setup, jacobhinds, Modding Q&A, and Somebody, Modding Q&A
party template dialog, cwr (credit), Modding Q&A